/* 
 * Copyright (c)	Insighten Intelligent Equipment co.,LTD
 * 
 *  Use of this software is controlled by the terms and conditions found
 *  in the license agreement under which this software has been supplied.
 * 
 */
/** 
 * @file udp_lib.c
 * @brief udp socket library.
 * @author taco [tacoee@gmail.com]
 * @version 0.1
 * @date 2010-09-06
 */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "udp_lib.h"
#include "types.h"

UdpSocket *udp_socket_new()
{
	UdpSocket *thiz;

	thiz = calloc(sizeof(UdpSocket), 1);

	return thiz;
}

Ret udp_socket_destroy(UdpSocket *thiz)
{
	if (thiz != NULL) {
		if (thiz->fd != -1) {
			close(thiz->fd);
		}
		thiz->buf = NULL;
		free(thiz);
		return RET_OK;
	}
	return RET_NO_TARGET;
}
Ret udp_socket_init(UdpSocket *thiz, char *lckaddr, char *remaddr, int port, SocketAttr *attr)
{
	int ret;

	if (thiz != NULL) {
		thiz->fd = -1;
		thiz->port = port;
		if(lckaddr != NULL) {
			memset(&thiz->lckaddr, 0, sizeof(struct sockaddr_in));
			thiz->lckaddr.sin_family = AF_INET;
			if (lckaddr) {
				ret = inet_aton(lckaddr, &thiz->lckaddr.sin_addr);
			}
			if (ret < 0) {
				return RET_FAIL;
			}
			thiz->lckaddr.sin_port = htons(port);
		}

		if (remaddr != NULL) {
			memset(&thiz->remaddr, 0, sizeof(struct sockaddr_in));
			thiz->remaddr.sin_family = AF_INET;
			if (remaddr) {
				ret = inet_aton(remaddr, &thiz->lckaddr.sin_addr);
			}
			if (ret < 0) {
				return RET_FAIL;
			}

			thiz->remaddr.sin_port = htons(port);
		}
		thiz->attr = attr;
		return RET_OK;
	}

	return RET_NO_TARGET;
}

Ret udp_socket_set_local_addr(UdpSocket *thiz, char *lckaddr, int port)
{
	int ret; 
	return_val_if_fail(thiz != NULL && lckaddr != NULL, RET_FAIL);

	memset(&thiz->lckaddr, 0, sizeof(struct sockaddr_in));
	thiz->lckaddr.sin_family = AF_INET;
	if (lckaddr) {
		ret = inet_aton(lckaddr, &thiz->lckaddr.sin_addr);
	}
	if (ret < 0) {
		return RET_FAIL;
	}
	thiz->lckaddr.sin_port = htons(port);

	return RET_OK;
}
Ret udp_socket_set_remote_addr(UdpSocket *thiz, char *remaddr, int port)
{
	int ret;

	return_val_if_fail(thiz != NULL && remaddr != NULL, RET_FAIL);
	memset(&thiz->remaddr, 0, sizeof(struct sockaddr_in));
	thiz->remaddr.sin_family = AF_INET;
	if (remaddr) {
		ret = inet_aton(remaddr, &thiz->lckaddr.sin_addr);
	}
	if (ret < 0) {
		return RET_FAIL;
	}

	thiz->remaddr.sin_port = htons(port);

	return RET_FAIL;
}
Ret udp_socket_create_bind(UdpSocket *thiz)
{
	if (thiz != NULL) {
		thiz->fd = socket(AF_INET, SOCK_DGRAM, 0);
		if (thiz->fd == -1) {
			return RET_FAIL;
		}
		if (thiz->attr) {
			set_sock_opt(thiz->fd, thiz->attr);
		}
		if (bind(thiz->fd, (struct sockaddr *)&thiz->lckaddr,thiz->lckaddr_len) < 0) {
			return RET_FAIL;
		}
		return RET_OK;
	}

	return RET_NO_TARGET;
}

Ret udp_socket_connet(UdpSocket *thiz)
{
	int ret;
	struct sockaddr sa;
	
	sa.sa_family = AF_UNSPEC;

	if (thiz != NULL && thiz->fd != -1) {
		if (thiz->connect && 
			!(thiz->sock_flag & SOCK_FLG_CONNECTED)) {
			ret = connect(thiz->fd,(struct sockaddr*)&thiz->remaddr, thiz->remaddr_len);
			if (ret < 0) {
				return RET_FAIL;
			}
			thiz->sock_flag |= SOCK_FLG_CONNECTED;
		} else if (!thiz->connect) {
			ret = connect(thiz->fd, &sa, sizeof(sa));
			if (ret < 0) {
				return RET_FAIL;
			}
			thiz->sock_flag &=~SOCK_FLG_CONNECTED;
		}

		return RET_OK;
	}

	return RET_NO_TARGET;
}

Ret udp_socket_buf_init(UdpSocket *thiz, RwBuf *buf)
{
	if (buf == NULL || thiz == NULL) {
		return RET_IGNORED;
	}
	thiz->buf = buf;

	return RET_OK;
}

int udp_socket_send(UdpSocket *thiz, int size)
{
	int ret = -1;
	int wd = -1;

	if (thiz != NULL && thiz->buf != NULL) {
		if (thiz->connect && (thiz->sock_flag & SOCK_FLG_CONNECTED)) {
			wd = write(thiz->fd, thiz->buf->mem + thiz->buf->rpos, size);
		} else {
			wd = sendto(thiz->fd, thiz->buf->mem + thiz->buf->rpos, size, 0, 
					(struct sockaddr *)&thiz->remaddr, thiz->remaddr_len);
			if (thiz->connect) {
				ret = connect(thiz->fd, (struct sockaddr *)&thiz->remaddr, thiz->remaddr_len);
				if (ret == 0) {
					thiz->sock_flag |= SOCK_FLG_CONNECTED;
				}
			}
		}
		if (wd < 0) {
			if (errno == EAGAIN || errno == EWOULDBLOCK) {
				return -2; /*no buffer space*/
			} else {
				return -1; /*other reasons cause error*/
			}
		}
	}
	return wd;
}

int udp_socket_recv(UdpSocket *thiz, int size)
{
	int ret = -1;
	int rd = -1;
	if (thiz != NULL && thiz->buf != NULL) {
		if (thiz->connect && (thiz->sock_flag & SOCK_FLG_CONNECTED)) {
			rd = read(thiz->fd, thiz->buf->mem + thiz->buf->wpos, size);
		} else {
			rd = recvfrom(thiz->fd, thiz->buf->mem + thiz->buf->wpos, size, 0, 
					(struct sockaddr *)&thiz->remaddr, &thiz->remaddr_len);
			if (thiz->connect) {
				ret = connect(thiz->fd, (struct sockaddr *)&thiz->remaddr, thiz->remaddr_len);
				if (ret == 0) {
					thiz->sock_flag |= SOCK_FLG_CONNECTED;
				}
			}
		}
		if (rd < 0) {
			if (errno == EAGAIN || errno == EWOULDBLOCK) {
				return -2;
			} else {
				return -1;
			}
		}
	}

	return rd;
}
